{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 注意！请根据提示操作！\n",
    "\n",
    "如果当页面加载完成，你还是看到了这条提示\n",
    "\n",
    "那么你的notebook并未被信任，导致代码无法正常运行\n",
    "\n",
    "点击右上角的`不可信`，将本notebook列入可信列表，即可正常运行。\n",
    "\n",
    "![可信按钮](./mgr/trust.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 类与对象\n",
    "\n",
    "生活中我们会把一系列具有相似特点的事物归位一类。\n",
    "\n",
    "例如：\n",
    "\n",
    "有轮子，用汽油，有方向盘，踩下油门会前进的，不管上面的牌子是雪弗兰还是宝马，一般我们都会把他们当成汽车。\n",
    "\n",
    "所有的汽车都有相似的属性：\n",
    "\n",
    "```\n",
    "有轮子\n",
    "有发动机\n",
    "有方向盘\n",
    "有座椅\n",
    "...\n",
    "```\n",
    "\n",
    "他们也有相似的方法：\n",
    "\n",
    "```\n",
    "踩下油门会前进\n",
    "踩下刹车会停车\n",
    "按下钥匙会开门\n",
    "...\n",
    "```\n",
    "\n",
    "这样的一系列相似的属性和方法的聚合，我们就叫类，而具体某个单独的特例，就叫对象。\n",
    "例如，汽车是类，浙B12345的玛莎拉蒂汽车就是一个汽车的对象。\n",
    "又例如，人是类，姚明就是一个人的对象。\n",
    "\n",
    "下面以一个游戏中的角色为例，对类和对象进一步讲述。\n",
    "\n",
    "试想一个游戏角色获取经验后升级的场景。假设输入值是获得的经验，每100点经验升一级，开始时经验为0，等级为1，要求你记录当前每个角色的等级和经验情况。\n",
    "\n",
    "下面就是用类和对象的方法完成的实现："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Player:  # 类的名字，一般习惯首字母大写\n",
    "    def __init__(self, name):  # 初始化角色所有的属性，其中name是初始化时传入的属性，self有点复杂，可以查看下方的详细说明\n",
    "        self.name = name  # 角色名字\n",
    "        self.exp = 0  # 角色经验值，初始为0\n",
    "        self.level = 1  # 角色等级，初始为1\n",
    "    \n",
    "    def add_exp(self, exp):  # 类的方法：实际上是增加经验的函数，exp为获取到的经验值\n",
    "        self.exp += exp  # 此处等于self.exp = self.exp + exp，注意exp和self.exp是两个不同的变量。exp是传入的参数值，self.exp是对象中保存着的属性值。\n",
    "        if self.exp >= 100:  # 经验值超过100时会升级。\n",
    "            n = self.exp // 100\n",
    "            self.level += n  # 每100点经验升一级\n",
    "            self.exp = self.exp % 100  # 如果有升级，获取升完级后剩余的经验\n",
    "\n",
    "jianna = Player('吉安娜')  # 传入'吉安娜'这个名字给初始化函数中的name，并初始化一个Player的对象赋予jianna变量，所以运行完之后，jianna其实是一个Player类的对象\n",
    "mfla = Player('玛法里奥')  # 如上，传入'玛法里奥'这个名字初始化\n",
    "aess = Player('阿尔萨斯')\n",
    "\n",
    "\n",
    "# 中间可以经历一系列的经验获取过程\n",
    "jianna.add_exp(50)\n",
    "mfla.add_exp(73)\n",
    "aess.add_exp(85)\n",
    "jianna.add_exp(234)\n",
    "aess.add_exp(37)\n",
    "jianna.add_exp(99)\n",
    "\n",
    "print(f'{jianna.name}的等级为：{jianna.level}，经验值为：{jianna.exp}')\n",
    "print(f'{mfla.name}的等级为：{mfla.level}，经验值为：{mfla.exp}')\n",
    "print(f'{aess.name}的等级为：{aess.level}，经验值为：{aess.exp}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在Python中，由两根下划线包围的函数一般都具有特别的含义，具有系统内置的一些功能。\n",
    "\n",
    "例如上述代码中的`__init__()`为类的初始化函数，当类被初始化的时候，自动会被调用。\n",
    "\n",
    "在上述代码中，`__init__()`函数会被调用三次，分别在\n",
    "\n",
    "```\n",
    "jianna = Player('吉安娜')\n",
    "mfla = Player('玛法里奥')\n",
    "aess = Player('阿尔萨斯')\n",
    "```\n",
    "\n",
    "这三处被调用。三次调用时，name参数分别为'吉安娜'， '玛法里奥'和'阿尔萨斯'。\n",
    "\n",
    "而self是一个特殊的参数，在类中的函数定义时，第一个参数一般都需要设为self，但实际调用函数时，不需要传参数给self。\n",
    "\n",
    "self中保存了所有对象相关的属性和方法，可以说self就是这个对象本身的所有内容。\n",
    "\n",
    "例如对于jianna对象而言，self.name的值为'吉安娜'，\n",
    "\n",
    "但对于mfla对象而言，self.name的值便为'玛法里奥'。\n",
    "\n",
    "如果还是不清楚，你可以理解成类似如下的概念：\n",
    "\n",
    "```\n",
    "jianna.add_exp(53)\n",
    "```\n",
    "\n",
    "其实可以理解成\n",
    "\n",
    "```\n",
    "add_exp(jianna, 53)\n",
    "```\n",
    "\n",
    "这样add_exp第一个self获取到的就是jianna，那么就会在jianna的exp属性中加上53，而不影响其它角色的属性。\n",
    "\n",
    "\n",
    "## 为什么要用类和对象？\n",
    "\n",
    "类和对象的好处是对于事物的抽象，便于模块化和维护性，主要是在大型工程中让代码变得易读易维护易协同工作，同时复用率高，逻辑清晰。\n",
    "\n",
    "例如，此时如果希望能在升级时加一句提示语，只需要修改一行代码即可："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Player:  # 类的名字，一般习惯首字母大写\n",
    "    def __init__(self, name):  # 初始化角色所有的属性，其中name是初始化时传入的属性，self有点复杂，可以查看下方的详细说明\n",
    "        self.name = name  # 角色名字\n",
    "        self.exp = 0  # 角色经验值，初始为0\n",
    "        self.level = 1  # 角色等级，初始为1\n",
    "    \n",
    "    def add_exp(self, exp):  # 类的方法：实际上是增加经验的函数，exp为获取到的经验值\n",
    "        self.exp += exp  # 此处等于self.exp = self.exp + exp，注意exp和self.exp是两个不同的变量。exp是传入的参数值，self.exp是对象中保存着的属性值。\n",
    "        if self.exp >= 100:  # 经验值超过100时会升级。\n",
    "            n = self.exp // 100\n",
    "            self.level += n  # 每100点经验升一级\n",
    "            print(f'恭喜：{self.name}升了{n}级')\n",
    "            self.exp = self.exp % 100  # 如果有升级，获取升完级后剩余的经验\n",
    "\n",
    "jianna = Player('吉安娜')  # 传入'吉安娜'这个名字给初始化函数中的name，并初始化一个Player的对象赋予jianna变量，所以运行完之后，jianna其实是一个Player类的对象\n",
    "mfla = Player('玛法里奥')  # 如上，传入'玛法里奥'这个名字初始化\n",
    "aess = Player('阿尔萨斯')\n",
    "\n",
    "\n",
    "# 中间可以经历一系列的经验获取过程\n",
    "jianna.add_exp(50)\n",
    "mfla.add_exp(73)\n",
    "aess.add_exp(85)\n",
    "jianna.add_exp(234)\n",
    "aess.add_exp(37)\n",
    "jianna.add_exp(99)\n",
    "\n",
    "print(f'{jianna.name}的等级为：{jianna.level}，经验值为：{jianna.exp}')\n",
    "print(f'{mfla.name}的等级为：{mfla.level}，经验值为：{mfla.exp}')\n",
    "print(f'{aess.name}的等级为：{aess.level}，经验值为：{aess.exp}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "同样如果需要攻击力、防御力和生命值的属性，每当升级的时候相关属性能升级，也可以稍作修改即可完成："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Player:  # 类的名字，一般习惯首字母大写\n",
    "    def __init__(self, name, ack, def_, hp):  # 初始化角色所有的属性，其中name是初始化时传入的属性，self有点复杂，可以查看下方的详细说明\n",
    "        self.name = name  # 角色名字\n",
    "        self.exp = 0  # 角色经验值，初始为0\n",
    "        self.level = 1  # 角色等级，初始为1\n",
    "        self.ack = ack  # 攻击力\n",
    "        self.def_ = def_  # 此处def代表防御力，但是由于def是关键字，无法用作变量名，所以在其最后加了下划线，防止与关键字重名。这也是编程中常用的技巧之一。\n",
    "        self.hp = hp  # 防御力\n",
    "    \n",
    "    def add_exp(self, exp):  # 类的方法：实际上是增加经验的函数，exp为获取到的经验值\n",
    "        self.exp += exp  # 此处等于self.exp = self.exp + exp，注意exp和self.exp是两个不同的变量。exp是传入的参数值，self.exp是对象中保存着的属性值。\n",
    "        if self.exp >= 100:  # 经验值超过100时会升级。\n",
    "            n = self.exp // 100\n",
    "            self.level += n  # 每100点经验升一级\n",
    "            print(f'恭喜：{self.name}升了{n}级')\n",
    "            self.ack += n * 1.5  # 假设每升一级加1.5的攻击力\n",
    "            self.def_ += n * 1  # 假设每升一级加1的防御力\n",
    "            self.hp += n * 5  # 假设每升一级加5点生命值\n",
    "            self.exp = self.exp % 100  # 如果有升级，获取升完级后剩余的经验\n",
    "    \n",
    "    def show_property(self):  # 把属性输出相关的也做到类之中，方便调用\n",
    "        print(f'{self.name}的等级为：{self.level}，经验值为：{self.exp}，攻击力为：{self.ack}，防御力为：{self.def_}，生命值为：{self.hp}')\n",
    "\n",
    "jianna = Player('吉安娜', 10, 2, 30)  # 现在不仅要传入名字，还要传入攻击力、防御力和生命值\n",
    "mfla = Player('玛法里奥', 5, 5, 80)\n",
    "aess = Player('阿尔萨斯', 8, 3, 100)\n",
    "\n",
    "\n",
    "# 中间可以经历一系列的经验获取过程\n",
    "jianna.add_exp(50)\n",
    "mfla.add_exp(73)\n",
    "aess.add_exp(85)\n",
    "jianna.add_exp(234)\n",
    "aess.add_exp(37)\n",
    "jianna.add_exp(99)\n",
    "\n",
    "jianna.show_property()\n",
    "mfla.show_property()\n",
    "aess.show_property()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "当然这样也可以做一个攻击的功能，例如每次攻击，对方会失去你的攻击力减去对方的防御力的生命值。如果生命值小于等于0则死亡。同时每次攻击给予一定的经验奖励："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Player:  # 类的名字，一般习惯首字母大写\n",
    "    def __init__(self, name, ack, def_, hp):  # 初始化角色所有的属性，其中name是初始化时传入的属性，self有点复杂，可以查看下方的详细说明\n",
    "        self.name = name  # 角色名字\n",
    "        self.exp = 0  # 角色经验值，初始为0\n",
    "        self.level = 1  # 角色等级，初始为1\n",
    "        self.ack = ack  # 攻击力\n",
    "        self.def_ = def_  # 此处def代表防御力，但是由于def是关键字，无法用作变量名，所以在其最后加了下划线，防止与关键字重名。这也是编程中常用的技巧之一。\n",
    "        self.hp = hp  # 防御力\n",
    "    \n",
    "    def add_exp(self, exp):  # 类的方法：实际上是增加经验的函数，exp为获取到的经验值\n",
    "        self.exp += exp  # 此处等于self.exp = self.exp + exp，注意exp和self.exp是两个不同的变量。exp是传入的参数值，self.exp是对象中保存着的属性值。\n",
    "        if self.exp >= 100:  # 经验值超过100时会升级。\n",
    "            n = self.exp // 100\n",
    "            self.level += n  # 每100点经验升一级\n",
    "            print(f'恭喜：{self.name}升了{n}级')\n",
    "            self.ack += n * 1.5  # 假设每升一级加1.5的攻击力\n",
    "            self.def_ += n * 1  # 假设每升一级加1的防御力\n",
    "            self.hp += n * 5  # 假设每升一级加5点生命值\n",
    "            self.exp = self.exp % 100  # 如果有升级，获取升完级后剩余的经验\n",
    "    \n",
    "    def attack(self, obj):\n",
    "        if self.hp <= 0:\n",
    "            print(f'你({self.name})已死亡，无法继续攻击！')\n",
    "            return False\n",
    "        if obj.hp <= 0:\n",
    "            print(f'对方({obj.name})已死亡，无法继续攻击！')\n",
    "            return False\n",
    "        obj.hp -= self.ack - obj.def_\n",
    "        self.add_exp(35)  # 一次普通攻击加35点经验\n",
    "        if obj.hp <= 0:\n",
    "            print(f'{obj.name}被杀死了！')\n",
    "            self.add_exp(obj.level * 100)  # 如果对方被杀死，那么将根据对方等级获得相应的经验值。\n",
    "    \n",
    "    def show_property(self):  # 把属性输出相关的也做到类之中，方便调用\n",
    "        print(f'{self.name}的等级为：{self.level}，经验值为：{self.exp}，攻击力为：{self.ack}，防御力为：{self.def_}，生命值为：{self.hp}')\n",
    "\n",
    "jianna = Player('吉安娜', 10, 2, 30)  # 现在不仅要传入名字，还要传入攻击力、防御力和生命值\n",
    "mfla = Player('玛法里奥', 5, 5, 80)\n",
    "aess = Player('阿尔萨斯', 8, 3, 100)\n",
    "\n",
    "\n",
    "# 互相打打杀杀\n",
    "jianna.attack(mfla)\n",
    "jianna.attack(mfla)\n",
    "jianna.attack(mfla)\n",
    "mfla.attack(aess)\n",
    "mfla.attack(jianna)\n",
    "mfla.attack(aess)\n",
    "aess.attack(mfla)\n",
    "aess.attack(jianna)\n",
    "aess.attack(jianna)\n",
    "aess.attack(jianna)\n",
    "aess.attack(jianna)\n",
    "aess.attack(jianna)\n",
    "aess.attack(jianna)\n",
    "jianna.attack(mfla)\n",
    "mfla.attack(aess)\n",
    "mfla.attack(jianna)\n",
    "mfla.attack(aess)\n",
    "\n",
    "# 展示属性\n",
    "jianna.show_property()\n",
    "mfla.show_property()\n",
    "aess.show_property()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 回顾已在使用的对象和类\n",
    "\n",
    "其实我们已经用过几次对象和类了，只是你可能还没察觉。例如："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "i = '1 2'  # 其实引号包括的字符串，就是一个字符串类的对象。\n",
    "a, b = i.split(' ')  # i其实就是一个对象，这里调用了i对象中的split函数，传入了一个空格字符串参数。所以在Python中，字符串变量其实是字符串类的一个对象，所以有句话叫Python中万物结对象。\n",
    "print(a)\n",
    "print(b)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "再比如："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "a = 'Hello World'\n",
    "print(a.lower())  # lower也是字符串类中的方法。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<hr>\n",
       "<link rel=\"stylesheet\" href=\"//cdn.bootcss.com/mdui/0.4.3/css/mdui.min.css\">\n",
       "<script src=\"./mgr/submit.js\"></script>\n",
       "<script src=\"./mgr/hide.js\"></script>\n",
       "<div class=\"mdui-container-fluid\">\n",
       "    <div class=\"mdui-col-xs-3\">\n",
       "        <button class=\"mdui-btn mdui-btn-raised mdui-ripple mdui-color-blue-grey-500\" onclick=\"next_page(-1)\">上一章</button>\n",
       "    </div>\n",
       "    <div class=\"mdui-col-xs-3\">\n",
       "        <button class=\"mdui-btn mdui-btn-raised mdui-ripple mdui-color-blue-grey-700\" onclick=\"window.open('/tree')\">目录</button>\n",
       "    </div>\n",
       "    <div class=\"mdui-col-xs-3\">\n",
       "        <button class=\"mdui-btn mdui-btn-raised mdui-ripple mdui-color-blue-grey-900\" onclick=\"next_page(1)\">下一章</button>\n",
       "    </div>\n",
       "</div>\n",
       "<div id=\"html_output\">\n",
       "</div>\n"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "%%html\n",
    "<hr>\n",
    "<link rel=\"stylesheet\" href=\"//cdn.bootcss.com/mdui/0.4.3/css/mdui.min.css\">\n",
    "<script src=\"./mgr/submit.js\"></script>\n",
    "<script src=\"./mgr/hide.js\"></script>\n",
    "<div class=\"mdui-container-fluid\">\n",
    "    <div class=\"mdui-col-xs-3\">\n",
    "        <button class=\"mdui-btn mdui-btn-raised mdui-ripple mdui-color-blue-grey-500\" onclick=\"next_page(-1)\">上一章</button>\n",
    "    </div>\n",
    "    <div class=\"mdui-col-xs-3\">\n",
    "        <button class=\"mdui-btn mdui-btn-raised mdui-ripple mdui-color-blue-grey-700\" onclick=\"window.open('/tree')\">目录</button>\n",
    "    </div>\n",
    "    <div class=\"mdui-col-xs-3\">\n",
    "        <button class=\"mdui-btn mdui-btn-raised mdui-ripple mdui-color-blue-grey-900\" onclick=\"next_page(1)\">下一章</button>\n",
    "    </div>\n",
    "</div>\n",
    "<div id=\"html_output\">\n",
    "</div>"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
